﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace Simulator
{
	public partial class HintControl : UserControl
	{
		class Hint
		{
			public RectangleF Bounds;
			public readonly string Text;

			public Hint (string text)
			{
				this.Text = text;
				this.Bounds = RectangleF.Empty;
			}

			public RectangleF GetCloseButtonBounds (SizeF closeButtonSize)
			{
				return new RectangleF (
					x: this.Bounds.Right - CloseButtonPadding - closeButtonSize.Width,
					y: this.Bounds.Top + CloseButtonPadding,
					width: closeButtonSize.Width,
					height: closeButtonSize.Height);
			}
		}

		const int MaxVisibleHintCount = 3;

		const int CloseButtonPadding = 6;
		const int DefaultClientSizeWidth = 300;
		const int HintTextPadding = 3;
		const int HintRectangleSpacing = 5;

		//                             <-- Hint.Width = ClientSize.Width -->
		//
		//                             -------------------------------------                                      ^
		//          HintTextPadding -> |                                   | <- CloseButtonPadding               |
		//                             |  abcde abcde abcde       -------  |                                     |
		//                             |  1234567890 123456789    |     |  |                                     |
		//                             |  12345 32187932 79871    -------  |                                     | Hint.Height
		//                             |  ewuy eewq urry qewie             | <- minimum CloseButtonPadding       |
		//  minimum HintTextPadding -> |                                   |                                     |
		//                             -------------------------------------                                      -
		//                              ^                       ^         ^
		//                              |                       |         |
		//                        HintTextPadding               | CloseButtonPadding
		//                                                      |
		//                                     max (HintTextPadding, CloseButtonPadding)

		Control _parent;

		/// <summary>
		/// This list includes hints currently displayed and hints waiting to be displayed.
		/// Only a maximum of MaxVisibleHintCount hints are displayed at a time, the first from this list.
		/// </summary>
		List<Hint> _hintsToShow;

		List<Hint> _hiddenHints;
		SizeF _closeButtonSize;
		int _hoverCloseButtonIndex = -1;

		public HintControl ()
		{
			//this.Font = SystemFonts.StatusFont;
			//this.BackColor = Color.Transparent;
			//this.ForeColor = SystemColors.InfoText;

			InitializeComponent ();

			SetStyle (ControlStyles.OptimizedDoubleBuffer, true);
			SetStyle (ControlStyles.UserPaint, true);
			SetStyle (ControlStyles.AllPaintingInWmPaint, true);
			SetStyle (ControlStyles.ResizeRedraw, true);

			_parent = this.Parent;
			this.Size = new Size (DefaultClientSizeWidth, 0);
			_hintsToShow = new List<Hint> ();
			_hiddenHints = new List<Hint> ();
		}

		int VisibleHintCount
		{
			get { return Math.Min (_hintsToShow.Count, MaxVisibleHintCount); }
		}

		void CalculateAndSetBounds ()
		{
			if (_hintsToShow.Count == 0)
			{
				this.ClientSize = new Size (DefaultClientSizeWidth, 0);
			}
			else
			{
				using (var g = this.CreateGraphics ())
				{
					SizeF charSize = g.MeasureString ("A", SystemFonts.StatusFont, 1000, StringFormat.GenericDefault);

					// rounded to int to avoid drawing with antialiasing
					_closeButtonSize = new SizeF ((float) (int) (charSize.Height / 1.3F), (float) (int) (charSize.Height / 1.3F));

					float textLayoutWidth = this.ClientSize.Width
						- HintTextPadding
						- Math.Max (HintTextPadding, CloseButtonPadding)
						- _closeButtonSize.Width
						- CloseButtonPadding;

					SizeF textLayoutArea = new SizeF (textLayoutWidth, 100000);

					float y = 0;

					for (int i = 0; i < VisibleHintCount; i++)
					{
						Hint h = _hintsToShow [i];

						SizeF textSize = g.MeasureString (h.Text, SystemFonts.StatusFont, textLayoutArea, StringFormat.GenericDefault);
						float hintWidth = ClientSize.Width;
						float hintHeight = Math.Max (HintTextPadding + textSize.Height + HintTextPadding, CloseButtonPadding + _closeButtonSize.Height + CloseButtonPadding);
						hintHeight = (float) Math.Ceiling (hintHeight);
						h.Bounds = new RectangleF (0, y, hintWidth, hintHeight);

						y += hintHeight;
						if (i < VisibleHintCount - 1)
							y += HintRectangleSpacing;
					}

					this.ClientSize = new Size (ClientSize.Width, (int) Math.Ceiling (y));
					this.Location = new Point (10, _parent.ClientSize.Height - 10 - this.Height);
				}
			}

			Invalidate ();
		}

		protected override void OnParentChanged (EventArgs e)
		{
			base.OnParentChanged (e);

			if (_parent != null)
				_parent.ClientSizeChanged -= _parent_ClientSizeChanged;

			if (this.Parent != null)
				this.Parent.ClientSizeChanged += _parent_ClientSizeChanged;

			_parent = this.Parent;

			if (_parent != null)
			{
				CalculateAndSetBounds ();
			}
		}

		void _parent_ClientSizeChanged (object sender, EventArgs e)
		{
			CalculateAndSetBounds ();
		}

		public void ShowHint (string hintText, bool forceShowAfterItWasHidden = false)
		{
			if (forceShowAfterItWasHidden)
			{
				_hiddenHints.RemoveAll (h_ => h_.Text == hintText);
			}
			else
			{
				if (_hiddenHints.Find (h_ => h_.Text == hintText) != null)
					return;
			}

			Hint h = _hintsToShow.Find (h_ => h_.Text == hintText);
			if (h == null)
			{
				h = new Hint (hintText);
			}
			else
			{
				// move existing hint to beginning of list
				_hintsToShow.Remove (h);
			}

			// insert hint at beginning of list
			_hintsToShow.Insert (0, new Hint (hintText));

			CalculateAndSetBounds ();
		}

		void HideHintInternal (Hint h)
		{
			_hintsToShow.Remove (h);
			CalculateAndSetBounds ();

			Debug.Assert (_hiddenHints.Contains (h) == false);
			_hiddenHints.Add (h);
		}

		public void HideHint (string hintText)
		{
			Hint h = _hintsToShow.Find (h_ => h_.Text == hintText);
			if (h != null)
				HideHintInternal (h);
		}

		protected override void OnSystemColorsChanged (EventArgs e)
		{
			base.OnSystemColorsChanged (e);
			Invalidate ();
		}

		protected override void OnPaint (PaintEventArgs e)
		{
			base.OnPaint (e);

			// Turn on antialiasing before drawing the hint
			e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

			for (int i = 0; i < VisibleHintCount; i++)
			{
				Hint h = _hintsToShow [i];

				// background and edges
				e.Graphics.FillRectangle (SystemBrushes.Info, h.Bounds);
				e.Graphics.DrawRectangle (SystemPens.InfoText, h.Bounds.X, h.Bounds.Y, h.Bounds.Width - 1, h.Bounds.Height - 1);

				// text
				var bounds = h.Bounds;
				bounds.X += HintTextPadding;
				bounds.Y += HintTextPadding;
				bounds.Width -= (HintTextPadding + Math.Max (HintTextPadding, CloseButtonPadding) + _closeButtonSize.Width + CloseButtonPadding);
				bounds.Height = 100000;
				e.Graphics.DrawString (h.Text, SystemFonts.StatusFont, SystemBrushes.InfoText, bounds, StringFormat.GenericDefault);

				// close button
				var closeButtonBounds = h.GetCloseButtonBounds (_closeButtonSize);
				e.Graphics.DrawRectangle (SystemPens.InfoText, closeButtonBounds.X, closeButtonBounds.Y, closeButtonBounds.Width, closeButtonBounds.Height);
				e.Graphics.DrawLine (SystemPens.InfoText, closeButtonBounds.Left, closeButtonBounds.Top, closeButtonBounds.Right, closeButtonBounds.Bottom);
				e.Graphics.DrawLine (SystemPens.InfoText, closeButtonBounds.Left, closeButtonBounds.Bottom, closeButtonBounds.Right, closeButtonBounds.Top);
			}
		}

		protected override void OnMouseMove (MouseEventArgs e)
		{
			base.OnMouseMove (e);

			int newHoverCloseButtonIndex = -1;

			for (int i = 0; i < VisibleHintCount; i++)
			{
				Hint h = _hintsToShow [i];
				var closeButtonBounds = h.GetCloseButtonBounds (_closeButtonSize);

				if (closeButtonBounds.Contains (e.X, e.Y))
				{
					newHoverCloseButtonIndex = i;
					break;
				}
			}

			if (_hoverCloseButtonIndex != newHoverCloseButtonIndex)
			{
				_hoverCloseButtonIndex = newHoverCloseButtonIndex;

				if (_hoverCloseButtonIndex == -1)
					this.Cursor = Cursors.Arrow;
				else
					this.Cursor = Cursors.Hand;
			}
		}

		protected override void OnMouseClick (MouseEventArgs e)
		{
			base.OnMouseClick (e);

			if (e.Button == MouseButtons.Left)
			{
				if (_hoverCloseButtonIndex != -1)
				{
					Hint h = _hintsToShow [_hoverCloseButtonIndex];
					HideHintInternal (h);
				}
			}
		}
	}
}
